/**
 * Function 的原型方法
 */

/**
 * 题1：模拟bind方法：创建一个新的函数，bind() 被调用时，这个新函数的 this 被指定为 bind() 的第一个参数，其余参数将作为新函数的参数，供调用时使用
 * 实现步骤：1) 判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况
 * 2) 保存当前函数引用，获取其余参数传入参数值
 * 3) 创建一个函数返回
 * 4) 函数内部使用 apply 绑定函数调用，需判断函数作为构造函数的情况，这时候需要传当前函数的 this 给 apply 调用，其余情况都传指定的上下文对象
 * 5) 对于构造函数，要保证原函数的原型对象上的属性不能丢失
 */
Function.prototype.fakeBind = function (context, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError('The first param of bind must be a function');
  }
  // 保存当前函数引用
  let self = this;
  let fBound = function () {
    // 判断是否为构造函数，new调用，当前函数引用 this 如果是 self 的实例
    let thisArg = this instanceof self ? this : context;
    // 注意：此处 arguments 是传给当前函数的参数
    self.apply(thisArg, args.concat(...arguments));
  }
  // 保证原函数的原型对象上的属性不能丢失
  fBound.prototype = Object.create(self.prototype);

  return fBound;
}

/**
 * 题2：模拟call方法：使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
 * 实现步骤：1) 判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况
 * 2) 判断传入上下文对象是否存在，如果不存在，则设置为 window
 * 3) 处理传入的参数，截取第一个参数后的所有参数 (直接拿args)
 * 4) 将函数作为上下文对象的一个属性，使用Symbol注入上下文，防止修改原同名属性
 * 5) 使用上下文对象来调用这个方法，并保存返回结果
 * 6) 删除上下文中该属性，返回结果
 * 注意：与 apply() 唯一区别：call() 方法接受的是一个参数列表，而 apply() 方法接受的是一个包含多个参数的数组
 */
Function.prototype.fakeCall = function (context, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError('The first param of bind must be a function');
  }
  context = context || window;
  let fn = Symbol('fn');
  context[fn] = this;
  let result = context[fn](...args);
  // 用完就丢弃渣男
  delete context[fn];
  return result;
}

/**
 * 题2：模拟apply方法：调用一个具有给定this值的函数，以及以一个数组（或类数组对象）的形式提供的参数
 * 实现步骤：1) 判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况
 * 2) 判断传入上下文对象是否存在，如果不存在，则设置为 window
 * 3) 处理传入的参数，截取第一个参数后的所有参数 (直接拿args)
 * 4) 将函数作为上下文对象的一个属性，使用Symbol注入上下文，防止修改原同名属性
 * 5) 使用上下文对象来调用这个方法，并保存返回结果
 * 6) 删除上下文中该属性，返回结果
 * 注意：这里只接收两个参数
 */
Function.prototype.fakeApply = function (context, args) {
  if (typeof this !== 'function') {
    throw new TypeError('The first param of bind must be a function');
  }
  context = context || window;
  let fn = Symbol('fn');
  context[fn] = this;
  let result = context[fn](...args);
  // 用完就丢弃渣男
  delete context[fn];
  return result;
}